#pragma once

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <unordered_map>
#include <mutex>
#include "log.hpp"
#include "util.hpp"

namespace ns_index
{
    struct DocInfo
    {
        std::string _title;
        std::string _content;
        std::string _url;
        uint64_t _doc_id;
    };

    struct InvertedElem
    {
        std::string _keyword; //关键字
        uint64_t _doc_id; //文档id
        int _weight; //文档权值
    };

    typedef std::vector<InvertedElem> InvertedList;

    class Index
    {
    public:
        static Index* getInstance()
        {
            if(_instance == nullptr)
            {
                _mutex.lock();
                if(_instance == nullptr) {
                    _instance = new Index;
                }
                _mutex.unlock();
            }
            return _instance;
        }

    public:
        //根据文档id获取文档内容
        DocInfo* getForwardIndex(const uint64_t doc_id)
        {
            if(doc_id < 0 || doc_id >= _forward_index.size()) {
                return nullptr;
            }
            return &_forward_index[doc_id];
        }
        //根据关键字获取文档id
        InvertedList* getInvertedList(const std::string& keyword)
        {
            auto iter = _inverted_index.find(keyword);
            if(iter == _inverted_index.end()) {
                return nullptr;
            }
            return &(iter->second);
        }

        //清空索引
        bool clearIndex()
        {
            _forward_index.clear();
            _inverted_index.clear();
        }

        //建立索引
        bool buildIndex(const std::string& input)
        {
            //读文件
            std::ifstream in(input, std::ios::in | std::ios::binary);
            if(!in.is_open()) {
                ns_log::lg(ns_log::FATAL, "open input file err");
                return false;
            }
            std::string line;
            int cnt = 0; //for debug
            int lineCount = ns_util::FileUtil::getLineCount(input);
            while(std::getline(in, line))
            {
                //建立正排索引
                DocInfo* doc = buildForwardIndex(line);
                if(doc == nullptr) {
                    ns_log::lg(ns_log::ERROR, "build forward %s err", line.c_str());
                    continue;
                }
                //建立倒排索引
                if(!buildInvertedIndex(*doc)) {
                    ns_log::lg(ns_log::ERROR, "build inverted %s err", line.c_str());
                    continue;
                }
                ++cnt;
                std::cout << "\r正在建立索引 -- " << cnt << " (total:" << std::to_string(lineCount) << ")";
                fflush(stdout);
            }
            in.close();
            return true;
        }

    private:
        //for debug
        // void debugForwardIndex(const DocInfo& doc)
        // {
        //     std::cout << "title: " << doc._title << std::endl;
        //     std::cout << "content: " << doc._content << std::endl;
        //     std::cout << "url: " << doc._url << std::endl;
        //     std::cout << "doc_id: " << doc._doc_id << std::endl;
        // }

        DocInfo* buildForwardIndex(const std::string& file)
        {
            std::vector<std::string> result;
            DocInfo doc;
            const std::string sep = "\3";
            //切分文件内容
            ns_util::StringUtil::SplitString(&result, file, sep);
            if(result.size() != 3) {
                std::cerr << "split " << file << " err" << std::endl;
                return nullptr;
            }
            //填充doc
            doc._title = result[0];
            doc._content = result[1];
            doc._url = result[2];
            doc._doc_id = _forward_index.size();
            //for debug
            // debugForwardIndex(doc);
            //建立索引
            _forward_index.push_back(doc);
            return &(_forward_index.back());
        }

        bool buildInvertedIndex(const DocInfo& doc)
        {
            struct word_cnt
            {
                int _title_cnt = 0;
                int _content_cnt = 0;
            };
            //统计词频时要忽略大小写 全部转为小写
            std::string title = ns_util::StringUtil::toLower(doc._title);
            std::string content = ns_util::StringUtil::toLower(doc._content);
            std::unordered_map<std::string, word_cnt> word_map;
            //对title进行分词
            std::vector<std::string> title_words;
            ns_util::JiebaUtil::CutString(title, &title_words);
            //统计title的词频
            for(const auto& word : title_words) {
                word_map[word]._title_cnt++;
            }
            //对content进行分词
            std::vector<std::string> content_words;
            ns_util::JiebaUtil::CutString(content, &content_words);
            //统计content的词频
            for(const auto& word : content_words) {
                word_map[word]._content_cnt++;
            }
            //计算权值和建立索引
            const int X = 10, Y = 1;
            for(auto& word_pair : word_map)
            {
                InvertedElem elem;
                elem._doc_id = doc._doc_id;
                elem._keyword = word_pair.first;
                elem._weight = word_pair.second._title_cnt * X + word_pair.second._content_cnt * Y;
                InvertedList& list = _inverted_index[word_pair.first];
                list.push_back(std::move(elem));
            }

            return true;
        }

    private:
        //正排索引 id - 文档内容
        std::vector<DocInfo> _forward_index;
        //倒排索引 关键字 - 倒排拉链
        std::unordered_map<std::string, InvertedList> _inverted_index;
    
    private:
        Index() { };
        Index(const Index&) = delete;
        Index& operator=(const Index&) = delete;

    private:
        static Index* _instance;
        static std::mutex _mutex;
    };

    Index* Index::_instance = nullptr;
    std::mutex Index::_mutex;
}